import { useCallback, useReducer, useState } from "react";
import { useMountedRef } from "utils";

interface State<D> {
  error: Error | null
  data: D | null;
  stat: "idle" | "loading" | "error" | "success"
}

const defaultInitialState: State<null> = {
  stat: "idle",
  data: null,
  error: null
}

const defaultConfig = {
  throwOnError: false
}

const useSafeDispatch = <T>(dispatch: (...args: T[]) => void) => {
  const mountedRef = useMountedRef()
  return useCallback((...args:T[])=> (mountedRef.current? dispatch(...args):void 0),[dispatch,mountedRef])
}

// TODO 用useReducer 改造
export const useAsync = <D>(
  initialState?: State<D>,
  initialConfig?: typeof
    defaultConfig
) => {

  const [state, dispatch] = useReducer((state: State<D>, action: Partial<State<D>>) => (
    { ...state, ...action }), {
    ...defaultInitialState,
    ...initialState
  })
  // const [state, setState] = useState<State<D>>({
  //   ...defaultInitialState,
  //   ...initialState
  // })

  const config = { ...defaultConfig, ...initialConfig }
  // const mountedRef = useMountedRef()
  const safeDispatch = useSafeDispatch(dispatch)

  // useState 直接传入函数的含义是：惰性初始化；所以，要用useState保存函数，不能直接传入函数
  const [retry, setRetry] = useState(() => () => { })

  // 使用useCallback 请求循环

  // const setData = (data: D) => setState({
  const setData = (data: D) => safeDispatch({
    data,
    stat: "success",
    error: null
  })

  // const setError = (error: Error) => setState({
  const setError = (error: Error) => safeDispatch({
    error,
    stat: "error",
    data: null
  })

  // run 用来触发异步请求
  const run = (promise: Promise<D>, runConfig?: { retry: () => Promise<D> }) => {
    if (!promise || !promise.then) {
      throw new Error("请传入 Promise 类型数据")
    }

    setRetry(() => () => {
      if (runConfig?.retry) {
        run(runConfig?.retry(), runConfig)
      }
    })

    // setState((prevState) => ({ ...prevState,...state, stat: "loading" })
    safeDispatch({stat:'loading'})

    return promise.then((data: D) => {
      // if (mountedRef.current) setData(data);
      setData(data);
      
      return data;
    }).catch(error => {
      // catch 会消化异常，如果不主动抛出，外面是接受不到异常的
      setError(error)

      if (config.throwOnError){ 
        return Promise.reject(error)
      }else{ 
        return error 
      }
    })
  }

  return {
    isIdle: state.stat === "idle",
    isLoading: state.stat === "loading",
    isError: state.stat === "error",
    isSuccess: state.stat === "success",
    run,
    setData,
    setError,
    retry,
    ...state
  }
}
